﻿
using EmperialApps.WeatherSpark.Data;
using EmperialApps.WeatherSpark.Internal;
using EmperialApps.WeatherSpark.Resources;
using System;
using System.ComponentModel;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Shapes;

namespace EmperialApps.WeatherSpark {

    public partial class WeatherSummary : WeatherControl {

        private readonly TimeLabelManager _dateLabels;
        private Scale _timeScale;
        private Scale _temperatureScale;

        public WeatherSummary( ) {
            if( DesignerProperties.IsInDesignTool )
                return;

            InitializeComponent( );

            var dateLabelTemplate = (DataTemplate)this.LayoutRoot.Resources["DateLabelTemplate"];
            this._dateLabels = new TimeLabelManager( dateLabelTemplate, this.GridLinesContainer, 0 ) { ShowHour = false, LabelOverride = AppResources.Graph_DateShortFormat };

            this.SizeChanged += this.OnSizeChanged;
        }

        private void OnSizeChanged( object sender, SizeChangedEventArgs e ) {
            this.OnVisibleElementsChanged( );
        }

        protected override void OnVisibleElementsChanged( ) {
            ClipToSize( this.LayoutRoot );

            // Set height of current time line.
            this.CurrentTimeLine.Y2 = this.LayoutRoot.ActualHeight - this.LayoutRoot.RowDefinitions[0].ActualHeight;

            base.OnVisibleElementsChanged( );
        }

        protected override Panel GetLayoutRoot( ) { return this.LayoutRoot; }

        protected override Forecast CoerceForecast( Forecast forecast ) {
            if( forecast.Start.Hour != 0 )
                forecast = CoerceForecastToWholeDay( forecast );

            // Trim end values if they do not form a whole day.
            int days = Math.Min( 6, forecast.Interval * forecast.Temperature.Count / ConvertValue.HoursPerDay );
            forecast = Forecast.Trim( forecast, 1 + days * ConvertValue.HoursPerDay );

            return forecast;
        }

        protected override void DisplayForecastDescription( Place place, string nickname ) {
            string description = place.Name;

            // If nickname and description start with the same city name, just show remaining description.
            int nicknameSeparator = nickname.IndexOf( ',' );
            int descriptionSeparator = description.IndexOf( ',' );
            if( descriptionSeparator > 0
             && descriptionSeparator == nicknameSeparator
             && description.Length - descriptionSeparator > 4
             && string.Compare( description, 0, nickname, 0, descriptionSeparator, StringComparison.CurrentCultureIgnoreCase ) == 0 )
                description = description.Substring( 1 + descriptionSeparator ).TrimStart( );

            this.ForecastDescription.Text = description + " ";
            this.ForecastTitle.Text = nickname;
        }

        protected override void DisplayForecast( Forecast forecast, DateTimeOffset previousStart ) {
            if( forecast == null || this.TemperatureTrace == null || this.GraphRow.ActualHeight == 0.0 )
                return;

            // Initialize scales and visual managers.
            double graphHeight = this.GraphRow.ActualHeight;
            DataValues temperature = this.GetTemperatureValues( forecast );
            int dataCount = forecast.Interval * (temperature.Count - 1);
            this._timeScale = new Scale( false, this.ActualWidth, new double[] { 0, dataCount } );
            this._temperatureScale = AkimaInterpolation.GetInterpolatedScale( true, graphHeight, temperature, 0.5 );
            this._dateLabels.Start = forecast.Start;

            // Draw temperature trace.
            var points = new PointCollection( );
            var interpolation = new AkimaInterpolation( temperature.Select( this._temperatureScale.ToScreen ), forecast.Interval );
            foreach( var p in interpolation.Interpolate( this._timeScale ) )
                points.Add( new Point( p.Item1, p.Item2 ) );
            this.TemperatureTrace.Points = points;

            // Draw background objects, glyphs, and grid lines.
            int span = 2;
            this.DisplayNightBackground( temperature );
            ClipTrace( this.PrecipitationTrace, forecast.PrecipitationPotential, CreateProportionalCircle, this._timeScale, span );
            ClipTrace( this.CloudCoverTrace, forecast.SkyCover, CreateProportionalSquare, this._timeScale, span );
            double maximumPosition = this.DisplayGridLines( dataCount - forecast.Interval );

            // Add indented label for highest grid line.
            var majorGridLineGeometry = (PathGeometry)this.MajorTemperatureGridLines.Data;
            var minorGridLineGeometry = (PathGeometry)this.MinorTemperatureGridLines.Data;
            var gridLineFigure =
                majorGridLineGeometry.Figures.Concat( minorGridLineGeometry.Figures )
                    .OrderBy( f => f.StartPoint.Y )
                    .FirstOrDefault( );

            if( gridLineFigure != null ) {
                double gridLineTemperaturePostion = gridLineFigure.StartPoint.Y;
                double gridLineTemperature = this._temperatureScale.FromScreen( gridLineTemperaturePostion );
                double gridLineTemperatureOffset = Math.Floor( gridLineTemperaturePostion - this.GridLineLabel.ActualHeight / 2.0 - 1.0 );

                this.GridLineLabel.Margin = new Thickness { Top = gridLineTemperatureOffset };
                this.GridLineLabel.Text = string.Format( "{0:0}\u200a\u00b0", gridLineTemperature );
                gridLineFigure.StartPoint = new Point( this.GridLineLabel.ActualWidth - 6.0, gridLineTemperaturePostion );
                this.ExtremeTemperatureLine.X1 = gridLineTemperaturePostion == this.ExtremeTemperatureLine.Y1 ? gridLineFigure.StartPoint.X : 0;
            }

            // Ensure date labels do not overlap temperature grid lines.
            const double RequiredSize = 20;
            double actualSize = graphHeight - maximumPosition;
            double sizeOffset = graphHeight - RequiredSize;
            if( actualSize < RequiredSize )
                sizeOffset -= actualSize + 4;
            this._dateLabels.Margin = new Thickness { Top = sizeOffset };
            this._dateLabels.DisplayVisuals( this._timeScale, ConvertValue.HoursPerDay, 0 );

            // Set position of current time line.
            double startOffset = -this._timeScale.ToScreen( forecast.Start.HoursFrom( previousStart ) );
            double hourOffset = DateTimeOffset.Now.HoursFrom( forecast.Start );
            double currentTimeOffset = this._timeScale.ToScreen( hourOffset );
            this.CurrentTimeLine.X1 = this.CurrentTimeLine.X2 = currentTimeOffset;

            // Display current values.
            hourOffset = Math.Max( 0, hourOffset );
            if( hourOffset < dataCount ) {
                string temperatureUnit = this.Units.GetTemperatureSymbol( );
                double currentTemperature = temperature.GetInterpolatedValue( hourOffset, ConvertValue.Identity );
                this.CurrentTemperatureLabel.Text = string.Format( "{0:0}\u200a\u00b0{1}", currentTemperature, temperatureUnit );

                string pressureUnit = this.Units.GetPressureSymbol( );
                double currentPressure = forecast.Pressure.GetInterpolatedValue( hourOffset, this.Units == Units.Metric ? ConvertValue.Identity : ConvertValue.ToInchesOfMercury );
                this.CurrentPressureLabel.Text = string.Format( "{0:0} {1}", currentPressure, pressureUnit );


                const string CurrentPercentLabelFormat = "{0:0}\u200a%";
                double currentHumidity = forecast.RelativeHumidity.GetInterpolatedValue( hourOffset, ConvertValue.ToPercentage );
                this.CurrentHumidityLabel.Text = string.Format( CurrentPercentLabelFormat, currentHumidity );

                double currentPrecipitation = forecast.PrecipitationPotential.GetInterpolatedValue( hourOffset, ConvertValue.ToPercentage );
                this.CurrentPrecipitationLabel.Text = string.Format( CurrentPercentLabelFormat, currentPrecipitation );

                double currentCloudCover = forecast.SkyCover.GetInterpolatedValue( hourOffset, ConvertValue.ToPercentage );
                this.CurrentCloudCoverLabel.Text = string.Format( CurrentPercentLabelFormat, currentCloudCover );


                var convert = this.Units == Units.Metric ? ConvertValue.Identity : ConvertValue.ToMilesPerHour;
                double currentWindSpeed = forecast.WindSpeed.GetInterpolatedValue( hourOffset, convert );
                if( Extensions.DistinguishZero( ref currentWindSpeed, 0 ) ) {
                    this.CurrentWindSpeedLabel.Text = "0";
                    this.CurrentWindDirectonLabel.Text = "";
                }
                else {
                    double currentWindDirection = forecast.WindDirection.GetInterpolatedValue( hourOffset, ConvertValue.Identity, ConvertValue.DegreesPerCircle );
                    CompassDirection currentCompassDirection = ConvertValue.ToCompassDirection( currentWindDirection );
                    this.CurrentWindSpeedLabel.Text = currentWindSpeed.ToString( "0" );
                    this.CurrentWindDirectonLabel.Text = currentCompassDirection.ToString( );
                }
            }
        }

        private double DisplayGridLines( int hourCount ) {
            var timeValues = Scale.GetRangeValues( ConvertValue.HoursPerDay, hourCount, ConvertValue.HoursPerDay );

            this.TimeGridLines.Update( Extensions.PathData, timeValues, ( t, geometry, values ) => {
                double lineHeight = this.LayoutRoot.ActualHeight - this.LayoutRoot.RowDefinitions[0].ActualHeight;
                foreach( double time in values ) {
                    double timePostion = this._timeScale.ToScreen( time );
                    geometry.Figures.Add( CreateVerticalLineFigure( timePostion, 0, lineHeight ) );
                }
            } );

            double temperatureStart = TemperatureInterval * Math.Ceiling( this._temperatureScale.Minimum / TemperatureInterval );
            var temperatureValues = Scale.GetRangeValues( temperatureStart, this._temperatureScale.Maximum, TemperatureInterval );
            return this.DisplayTemperatureGridLines(
                this.MinorTemperatureGridLines, this.MajorTemperatureGridLines, this.ExtremeTemperatureLine,
                temperatureValues, this._timeScale, this._temperatureScale, this._timeScale.ScreenSize );
        }

        protected override void DisplayNightBackground( DataValues temperature ) {
            this.NightBackground.Update( Extensions.PathClip, temperature, this.ClipNightBackground );
        }

        private void ClipNightBackground( Rectangle background, PathGeometry clip, DataValues temperature ) {
            this.ClipNightBackground( clip, temperature, this._timeScale, this._temperatureScale );
        }

    }

}
